/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.frames;

import ore.errors.OrekitException;
import ore.time.AbsoluteDate;

import java.util.NoSuchElementException;

/** 
 * Earth Orientation Parameters (either IAU1980 or IAU2000) as a date
 * ordered set.
 * 
 * @see EOP1980History
 * @see EOP2000History
 * 
 * @author Pascal Parraud
 * @author John Pritchard
 */
public abstract class EOPHistory
    extends ore.time.ChronologicalSet<EOP>
{
    protected static EOPHistory Instance;

    public final static EOPHistory Instance(){
        if (null != Instance)
            return EOPHistory.Instance;
        else
            throw OrekitException.createIllegalStateException("data not bound");
    }


    protected final static double Day = (86400.0);
    protected final static double Continuity = (5.0 * Day);


    private EOP previous;
    private EOP next;


    protected EOPHistory()
        throws OrekitException
    {
        super();
    }


    /**
     * Get the date of the first available Earth Orientation Parameters.
     *
     * @return the start date of the available data
     */
    public AbsoluteDate getStartDate() {
        return this.first().getDate();
    }
    /**
     * Get the date of the last available Earth Orientation Parameters.
     *
     * @return the end date of the available data
     */
    public AbsoluteDate getEndDate() {
        return this.last().getDate();
    }
    /**
     * Check Earth orientation parameters continuity.
     *
     * @param maxGap maximal allowed gap between entries (in seconds)
     * @exception OrekitException if there are holes in the data sequence
     */
    protected void checkEOPContinuity(final double maxGap) throws OrekitException {
        EOP preceding = null;
        for (EOP current : this) {

            if ((preceding != null)
                && (maxGap < (current.getDate().durationFrom(preceding.getDate()))))
            {
                throw new OrekitException("missing Earth Orientation Parameters between {0} and {1}",
                                          preceding, current);
            }
            else
                preceding = current;
        }
        if (null == preceding)
            throw new OrekitException("no Earth Orientation Parameters loaded");
    }
    /**
     * Get the interpolated value at some date for an indexed field of the entry.
     *
     * @param  date target date
     * @param  field The field to be interpolated
     * @return the interpolated value for the indexed field
     */
    protected double getInterpolatedField(AbsoluteDate date,
                                          EOP.Field field)
    {
        /*
         * Compute offsets assuming the current selection brackets the
         * date
        */
        double dtP = (this.previous == null) ? -1.0 : date.durationFrom(this.previous.getDate());
        double dtN = (this.next == null) ? -1.0 : this.next.getDate().durationFrom(date);
        /*
         * Check if bracketing was correct
         */
        if ((dtP < 0) || (dtN < 0)) {
            /*
             * Need to recompute brackets
             */
            if (!selectBracketingEntries(date)) {
                /*
                 * The specified date is outside of supported range
                 */
                return 0;
            }
            else {
                /*
                 * Recompute offsets
                 */
                dtP = date.durationFrom(previous.getDate());
                dtN = next.getDate().durationFrom(date);
            }
        }
        /*
         * Interpolate value
         */
        return (dtP * next.get(field) + dtN * previous.get(field)) /
               (dtP + dtN);
    }
    /**
     * Select the entries bracketing a specified date.
     *
     * If the date is either before the first entry or after the last
     * entry, previous and next will be set to null.
     *
     * @param  date target date
     * @return true if the date was found in the tables
     */
    protected boolean selectBracketingEntries(final AbsoluteDate date) {
        Brackets<EOP> brackets = this.brackets(date);
        this.next     = brackets.next;
        this.previous = brackets.previous;
        return brackets.isNotEmpty();
    }

}
